/*
 * encode image using different context, by edhong
 * Jan-March 2008
 *
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */


#include "SubbandBits.h"
#include "Characteristics.h"
#include "coder.h"

#define USG_STRING "[-bp %d:numbitplanes=15] [-bt %d:numbits] [-ct %d:CharacteristicType] [-cf %s:ctdfilename] [-e %d:encodectd=1] [-s %d:sortingMethod=0] [-d %d:debugflag=0] [-cl %d:colortype=2] [-cct %d:colorcharacteristictype=1] %s:imgfile %s:output_filename"

QccWAVWavelet Wavelet;
QccString WaveletFilename = QCCWAVWAVELET_DEFAULT_WAVELET;
QccString Boundary = "symmetric";
QccString outfilename;
QccString ctdfilename = "default.ctd";

int NumLevels = -1;
int NumSubbands;
int NumBitplanes = -1;
int numbits = -1; 
int CharacteristicType = 1;
int ColorCharacteristicType = 1;
int debugflag = 0;
int encodectd = 1;
int colortype = QCCIMGCOLORTYPE_YCbCr;

QccIMGImage InputImage;
QccWAVSubbandPyramid SubbandPyramid[3];

int ImageNumCols;
int ImageNumRows;
int sortNum = 0;

int main(int argc, char *argv[])
{
  unsigned long long flags[2];
  
  QccInit(argc, argv);

  QccWAVWaveletInitialize(&Wavelet);
  QccIMGImageInitialize(&InputImage);
  QccWAVSubbandPyramidInitialize(&SubbandPyramid[0]);
  QccWAVSubbandPyramidInitialize(&SubbandPyramid[1]);
  QccWAVSubbandPyramidInitialize(&SubbandPyramid[2]);
  
  if (QccParseParameters(argc, argv,
                         USG_STRING,
                         &NumBitplanes,
			 &numbits,
			 &CharacteristicType,
			 &ctdfilename,
			 &encodectd,
			 &sortNum,
			 &debugflag,
                         &colortype,
                         &ColorCharacteristicType,
                         InputImage.filename,
                         outfilename))
    QccErrorExit();

  if (NumBitplanes != -1 && numbits != -1)
    {
      QccErrorAddMessage("%s: Can only specify one of number of bitplanes and numbits",
                         argv[0]);
      QccErrorExit();
    }
  else if (NumBitplanes == -1 && numbits == -1) {
    NumBitplanes = 15; // default.. 
  }

  if (encodectd < 0 || encodectd > 1) {
    QccErrorAddMessage("%s: Encodectd must be 0 or 1",
		       argv[0]);
    QccErrorExit();

  }
     
  if (QccWAVWaveletCreate(&Wavelet, WaveletFilename, Boundary))
    {
      QccErrorAddMessage("%s: Error calling QccWAVWaveletCreate()",
                         argv[0]);
      QccErrorExit();
    }
  
  if  (QccIMGImageSetColorType(&InputImage, colortype)) 
    {
      QccErrorAddMessage("%s: Error calling QccIMGImageSetColorType()",
                         argv[0]);
      QccErrorExit();
    
    }

  if (QccIMGImageRead(&InputImage))
    {
      QccErrorAddMessage("%s: Error calling QccIMGImageRead()",
                         argv[0]);
      QccErrorExit();
    }
 
  ImageNumCols = InputImage.Y.num_cols;
  ImageNumRows = InputImage.Y.num_rows;


  NumLevels = computeNumWaveletLevels(ImageNumRows, ImageNumCols);
  /*  if (NumLevels == -1) {
    NumLevels = temp;
  }
  else if (NumLevels < -1)
    {
      QccErrorAddMessage("%s: Number of levels must be non-negative",
                         argv[0]);
      QccErrorExit();
    }
  else if (NumLevels > temp){
    QccErrorAddMessage("%s: Image too small to use %d wavelet levels",
		       argv[0], NumLevels);
    QccErrorExit();    
    }*/

  NumSubbands = QccWAVSubbandPyramidNumLevelsToNumSubbands(NumLevels);

  int actualnumbitplanes;
  if (NumBitplanes >0) {
     actualnumbitplanes = NumBitplanes;
   }
  else {
     // set bitplanes based on numbits.
       actualnumbitplanes = 1 + numbits / (ImageNumCols * ImageNumRows); 
  }
  
  InitAllC(NumSubbands, actualnumbitplanes); 
 
  if (getCharacteristicSet(CharacteristicType, flags ))
    {
      QccErrorExit();
    }

  if (QccIMGImageColor(&InputImage)) {
    if (addColorCharacteristicSet(ColorCharacteristicType, flags))
    {
        QccErrorExit();
    }
  }


  printf("Reading image %s, CharacteristicType %d, using ctdfile %s, writing to %s \n", 
	 InputImage.filename, CharacteristicType, ctdfilename, outfilename);
  printf("  parameters: ");
  if (NumBitplanes >0)
    printf ("%d bit-planes, ", NumBitplanes);
  if (numbits > 0) 
    printf("%d numbits, ", numbits);

  printf("%d wavelet levels,  encodectd= %d, sortingMethod = %d, debug=%d\n",
	 NumLevels, encodectd, sortNum, debugflag);
  printf("  parameters: colortype %d, colorcharacteristictype %d\n", colortype, 
        ColorCharacteristicType);
 
  int numComponents = 1;
  SubbandPyramid[0].num_levels = 0;
  SubbandPyramid[0].num_rows = ImageNumRows;
  SubbandPyramid[0].num_cols = ImageNumCols;
  SubbandPyramid[0].matrix = InputImage.Y.image;
  // subtract mean value of possible 8-bit values. 
  int i,j,k;
  for (i=0; i < SubbandPyramid[0].num_rows; i++) {
    for (j=0; j < SubbandPyramid[0].num_cols; j++) 
       SubbandPyramid[0].matrix[i][j] -= (255.0/2);
  }     

  if (QccIMGImageColor(&InputImage))
    {
      numComponents = 3;
      SubbandPyramid[1] = SubbandPyramid[0];
      SubbandPyramid[2] = SubbandPyramid[0];
      SubbandPyramid[1].matrix = InputImage.U.image;
      SubbandPyramid[2].matrix = InputImage.V.image;

    // make chrominance bands range from -127.5 to 127.5
    for (k=1; k < numComponents; k++) 
      for (i=0; i < SubbandPyramid[k].num_rows; i++) {
        for (j=0; j < SubbandPyramid[k].num_cols; j++) 
            switch (colortype) {
                case  QCCIMGCOLORTYPE_YCbCr:
                case QCCIMGCOLORTYPE_YUV:
                // do nothing, range already correct.
                    break;
                case QCCIMGCOLORTYPE_RGB:
                    SubbandPyramid[k].matrix[i][j] -= (255.0/2);
                   break;
                default: 
                   printf("Unimplemented color type %d\n", colortype);                   
            }
      } 
  

    }

  for (i=0; i < numComponents; i++) {
   if (QccWAVSubbandPyramidDWT(&SubbandPyramid[i],
			      NumLevels,
			      &Wavelet))
     {
      QccErrorAddMessage("%s: Error calling QccWAVSubbandPyramidDWT()",
			 argv[0]);
      QccErrorExit();
     }
  }



	 
  SubbandComponent *sbits; 
  if ((sbits = (SubbandComponent *)
       malloc(sizeof(SubbandComponent)*numComponents)) == NULL)
    {
      printf("%s: Error allocating memory\n", argv[0]);
      return(1);
    }

  int initialthreshpower = computeInitialThreshold(SubbandPyramid, numComponents);

  for (i =0; i < numComponents; i++) {
    if (debugflag!=0 && numComponents>1) 
      printf("**Component number %d\n", i);
    if ( CreateBits(&SubbandPyramid[i], 1 << initialthreshpower, &sbits[i], debugflag)){
      printf("(%s): Error calling CreateBits()\n", argv[0]);
      return(1);    
    }  
  }

  
  QccBitBuffer output_buffer; 
  QccENTArithmeticModel *model = NULL; 

  QccBitBufferInitialize(&output_buffer); 
  QccConvertToQccString(output_buffer.filename, outfilename);

  output_buffer.type = QCCBITBUFFER_OUTPUT; 
  if(QccBitBufferStart(&output_buffer)) {
    printf( "(%s): Error calling QccBitBufferStart\n", argv[0]); 
    return 1;
  }

  // initial stuff to stick in bitbuffer: numcols, numRow, 
  //  log_2 of initial threshold (as int), CharacteristicType,
  //  sorting_method (4 bits), actualnumbitplanes (5 bits),
  //  colortype (if necessary, 2 bits), colorcharacteristictype (4 bits), 
  //  encodectd, and  <encoding of ctd> if necessary. 

  int numbitsimagelength = 14;
  
  if (ImageNumCols >= (1<<numbitsimagelength) ||
      ImageNumRows >= (1<<numbitsimagelength)) {
    printf(" image too large to encode with upper limit bound\n");
    return (1);
  }
  int error = 0;
  error= QccBitBufferPutBits(&output_buffer, ImageNumCols, numbitsimagelength);
  error+=QccBitBufferPutBits(&output_buffer, ImageNumRows, numbitsimagelength);
  error+=QccBitBufferPutBits(&output_buffer, initialthreshpower, 4);
  error+=QccBitBufferPutBits(&output_buffer, CharacteristicType, 8);
  error+=QccBitBufferPutBits(&output_buffer, sortNum, 4);
  error+=QccBitBufferPutBits(&output_buffer, actualnumbitplanes, 5);
  if (QccIMGImageColor(&InputImage)) {
    error+=QccBitBufferPutBits(&output_buffer, colortype, 2);
    error+=QccBitBufferPutBits(&output_buffer, ColorCharacteristicType, 4);    
  }
  error+=QccBitBufferPutBit(&output_buffer, encodectd);
  
  if (error >0) {
    printf( "(%s): Error calling QccBitBufferPutBit(s)\n", argv[0]); 
    return 1;
  }

  contexttree ct;
  
  //    printf("*charset%llX %llX\n", flags[1], flags[0]);

  if (readctdfile(ctdfilename, flags, &ct)) {
      QccErrorAddMessage("%s: Error calling readctdfile()",
			 argv[0]);
      QccErrorExit();

  }


  if (debugflag==1) printcontexttree(&ct);

  int encode_eof = 0;  
  int numContexts;
  int *num_symbols;

  numContexts = ct.numleafnodes;
  num_symbols = (int *)malloc(sizeof(int)*numContexts);

  for ( i=0; i < numContexts; i++) {
    num_symbols[i] = 2;
  }

  printf("***BitBuffer bit_count after initial header: %d\n", output_buffer.bit_cnt);
  if (encodectd) {
    // encode ctd tree to output_buffer here. 
    if (encodectd_bitbuffer(&output_buffer, &ct)) {
      QccErrorAddMessage("%s: Error calling encodectd_bitbuffer()",
			 argv[0]);
      QccErrorExit();
        
    }
    printf("***BitBuffer bit_count after ctd encoding: %d\n", output_buffer.bit_cnt);
  }

  model = QccENTArithmeticEncodeStart2(num_symbols, 
				      numContexts, 
				      NULL, 
				      QCCENT_ANYNUMBITS, 
				      encode_eof 
				      ); 

  if (model == NULL) {
    printf( "%s: QccENTArithmeticEncodeStart2 call failed \n", argv[0]);
    return 1;
  }

  QccENTArithmeticSetModelAdaption(model, QCCENT_TKADAPTIVE);

  int result; 
  result = encode_subband_component(sbits, numComponents, model, &output_buffer, 
				    &ct, NumBitplanes, numbits, 
				    get_sorting_function(sortNum), debugflag);
  if (result!=0) 
    printf("%s: error, encode_subband_component returned %d\n", argv[0],
	   result);
	 


 if (QccENTArithmeticEncodeEnd(model, 
			       0, // first_context..
			       &output_buffer))
   printf("%s: error calling %s\n", argv[0], 
	  "QccENTArithmeticEncodeEnd"); 
  
 if(QccBitBufferEnd(&output_buffer))
   printf("%s: error calling %s\n", argv[0], 
	  "QccBitBufferEnd");
  
  QccENTArithmeticFreeModel(model); 

  printf("***BitBuffer bit_count: %d\n", output_buffer.bit_cnt);


  for (i=0; i < numComponents; i++) {
    SubbandComponentFree(&sbits[i]);
  }
  free(sbits);

  QccWAVWaveletFree(&Wavelet);
  QccIMGImageFree(&InputImage);

  QccExit;
}
